package supervise;

/**
 * Un classifieur multinomial
 */
import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;

import utils.Review;
import utils.StopWords;

public class ClassifieurMultinomial extends Classifieur {

	private HashMap<String, Double> betaPositif;
	private HashMap<String, Double> betaNegatif;
	
	/**
	 * Constructeur sans parametre. Ne fait rien.
	 * Permet de dériver des classes de Classifieurmultinomial
	 */
	public ClassifieurMultinomial() {
		
	}

	/**
	 * Crée un classifieur multinomial qui construit son dictionnaire et apprend à
	 * partir de l'ensemble donné
	 * 
	 * @param reviews
	 *            Liste de Reviews sur lesquelles construire son dictionnaire et
	 *            apprendre
	 * @param stopWords
	 *            Choix d'une liste de mots-outils à ignonrer.
	 * @param frequenceMaxi
	 *            La fréquence maximale d'apparition des mots, au delà de
	 *            laquelle le mot ne sera pas ajouté au dictionnaire. La
	 *            fréquence est calculée sur tous les mots du texte sauf les
	 *            mots-outils.
	 * @param parametreLissage
	 *            Paramètre de lissage de Laplace. Usuellement un petit epsilon.
	 */
	public ClassifieurMultinomial(ArrayList<Review> reviews,
			StopWords stopWords, double frequenceMini, double parametreLissage) {
		System.out.print(parametreLissage + "\t");
		String stopWordsPath = null;
		if (stopWords == StopWords.MEDIAWIKI) {
			stopWordsPath = "MediaWikiStopwords";
		} else if (stopWords == StopWords.SHORT) {
			stopWordsPath = "ShortStopwords";
		} else if (stopWords == StopWords.MEDIUM) {
			stopWordsPath = "MediumStopwords";
		} else if (stopWords == StopWords.LONG) {
			stopWordsPath = "LongStopwords";
		}
		construireDictionnaire(reviews, stopWordsPath, frequenceMini);
		apprendre(reviews, parametreLissage);
	}

	/**
	 * Crée les paramètres beta pour les 2 classes à partir de la liste de
	 * review donnée
	 * 
	 * @param reviews
	 *            Liste des reviews sur lesquelles apprendre
	 * @param parametreLissage
	 *            Paramètre de lissage de Laplace. Usuellement un petit epsilon.
	 */
	protected void apprendre(ArrayList<Review> reviews, double parametreLissage) {
		betaPositif = new HashMap<String, Double>();
		betaNegatif = new HashMap<String, Double>();
		String token;
		int totalPositif = 0, totalNegatif = 0;
		double norma, lisse;

		// On met tout le dictionnaire dans chaque vecteur
		for (String mot : dictionnaire) {
			betaPositif.put(mot, 0.d);
			betaNegatif.put(mot, 0.d);
		}

		// Pour chaque review
		for (Review review : reviews) {
			StringTokenizer tokenizer = new StringTokenizer(review.getTexte(),
					" ");
			// Pour chaque mot
			while (tokenizer.hasMoreTokens()) {
				token = tokenizer.nextToken().toLowerCase();
				// S'il appartient au dico
				if (dictionnaire.contains(token)) {
					// Si c'est dans une review positive
					if (review.isOpinionPositive()) {
						// On ajoute 1 occurrence dans le compte positif
						betaPositif.put(token, betaPositif.get(token) + 1);
						totalPositif++;
					}
					// Si c'est une review negative
					else {
						// On ajoute 1 dans le compte negatif
						betaNegatif.put(token, betaNegatif.get(token) + 1);
						totalNegatif++;
					}
				}
			}// tous les tokens
		}// toutes les review
//		System.out.println(totalPositif + " mots positifs " + totalNegatif + " mots negatifs");

		// On normalise
		for (String mot : betaNegatif.keySet()) {
			norma = betaNegatif.get(mot) * totalPositif / totalNegatif;
			betaNegatif.put(mot, norma);
		}

		// Pour éviter qu'un compte nul ça lisse Laplace
		for (String mot : betaPositif.keySet()) {
			lisse = (betaPositif.get(mot) + parametreLissage)
					/ (totalPositif + parametreLissage);
			betaPositif.put(mot, lisse);
		}
		for (String mot : betaNegatif.keySet()) {
			lisse = (betaNegatif.get(mot) + parametreLissage)
					/ (totalNegatif + parametreLissage);
			betaNegatif.put(mot, lisse);
		}

		/*
		 * System.out.println("motmot\tpositif\tnegatif"); for(String mot :
		 * dictionnaire) { System.out.println(mot + "\t" +
		 * comptePositif.get(mot) + "\t" + compteNegatif.get(mot)); }
		 */
	} // apprendre

	/**
	 * Evalue les probabilités d'une liste de reviews
	 * 
	 * @param reviews
	 *            La liste des reviews à évaluer
	 * @return Une map contenant comme clé l'identifiant de la review évaluée,
	 *         et comme valeur true si la review est évaluée positive, false si
	 *         elle est évaluée négative
	 */
	public HashMap<Integer, Boolean> classifier(ArrayList<Review> reviews) {
		StringTokenizer tokenizer;
		String token;
		HashMap<String, Double> compteReview = new HashMap<String, Double>();
		HashMap<Integer, Boolean> resultat = new HashMap<Integer, Boolean>();
		double occurrences;
		double probaPositif, probaNegatif;
		int pos =0, neg = 0;

		// Pour chaque review
		for (Review review : reviews) {
			compteReview.clear();
			tokenizer = new StringTokenizer(review.getTexte());
			// Pour chaque mot
			while (tokenizer.hasMoreTokens()) {
				token = tokenizer.nextToken().toLowerCase();
				occurrences = 0.d;
				// S'il est dans le dico
				if (dictionnaire.contains(token)) {
					// Si on l'a deja vu
					if (compteReview.containsKey(token)) {
						// On recupere son nombre d'apparitions
						occurrences = compteReview.get(token);
					}
					// Et on le met dans le compte avec nbOcc +1
					compteReview.put(token, occurrences + 1);
				}
			} // tous les tokens

			// On calcule sa proba d'être positif
			probaPositif = 0.d;
			probaNegatif = 0.d;
			// Pour chaque mot du document
			for (String mot : compteReview.keySet()) {
				probaPositif += Math.log(betaPositif.get(mot)) * compteReview.get(mot);
				probaNegatif += Math.log(betaNegatif.get(mot)) * compteReview.get(mot);
			}
			

			// On decide
			if (probaPositif > probaNegatif) {
				pos++;
//				System.out.println(probaPositif + " vs. " + probaNegatif);
				resultat.put(review.getId(), true);
			} else {
				neg++;
				resultat.put(review.getId(), false);
			}
		} // toutes les reviews
//		System.out.println("Je vote " + pos + " positives et " + neg + " negatives");
		return resultat;
	} // classifier
}
